#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "functions.h"

#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <ctype.h>

/* Commands main function list */
const GENERAL_COMMAND_LIST mainCommandList[8] =
{
   /* the command name,  the main function*/
    { "exit" ,  exit_func},
    { "pwd" ,   pwd_func},
    { "clear" , clear_func},
    { "ls" , ls_func},
    { "cd", cd_func},
    { "setenv", setenv_func},
    { "unsetenv", unsetenv_func},
    { "environ", environ_func}
};

FUNC_TYPE exit_func(int num_tokens, char* tokens[])
{
	return -2;
}

FUNC_TYPE pwd_func(int num_tokens, char* tokens[])
{
	char curWorkDir[1024];
	char *outfile={0};
	int k;
	for(k=0; k<num_tokens; k++) {
			if(!strcmp(tokens[k],">")) {
				outfile=(char *)malloc((strlen(tokens[k+1])+1)*sizeof(char));
				strcpy(outfile,tokens[k+1]);
				break;
			}
		}

    getcwd(curWorkDir, sizeof(curWorkDir));
    int out;
	int temp;
    if(outfile != NULL) {
    	out = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
    	temp = dup(1);
		dup2(out, 1);
    	close(out);
		printf("%s\n",curWorkDir);
		dup2(temp,1);
		close(temp);
		
    } else {
    	printf("%s\n",curWorkDir);
	}
	return 0;
}

FUNC_TYPE clear_func(int num_tokens, char* tokens[])
{
    printf ("\033[2J");
    printf ("\033[1;1H");
	return 0;
}

FUNC_TYPE ls_func(int num_tokens, char* tokens[])
{

    struct dirent *dbuf;
    DIR *dp;
    struct stat stbuf;
    int lev,i;
    char cwd[128],cwdd[128];
    redirection redir;


        getcwd( cwd, sizeof (cwd) );
        redir = io_redirection(num_tokens,  tokens);

        if ( redir.f_support ) {
            printf( "--%s\n", "sorry dude,i'm not that smart" );
            return 0 ;
        }

        for (lev = 1 ; lev < num_tokens ; lev++ ) {
        	strcat(tokens[lev],"/");
        }

        if (redir.f_rdirect & 3 ) {

            //input to be the output

        }


        lev = 1;
        if (num_tokens > 1 ) {
        	cd_func(num_tokens,tokens);
        	getcwd( cwdd, sizeof (cwdd) );
        	dp = opendir( cwdd);

        } else {
           dp = opendir( cwd );
        }

        printf( "\n" );

if (fork() == 0) {

    if(redir.outfile != NULL) {
    	redir.out = open(redir.outfile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
    	dup2(redir.out, 1);
      	close(redir.out);
        redir.outfile = NULL;
    }

    while ((dbuf = readdir( dp ))) {

    	if (! strncmp( dbuf->d_name, ".",1 ) || ! strcmp( dbuf->d_name, ".." )  ) {
    		continue;
    	}

    	for (i = 0; i < lev; ++i)
           if (stat( dbuf->d_name, &stbuf ) < 0)
                {
                    printf( "\n" );
                continue;
                }

    	switch (stbuf.st_mode & S_IFMT) {
           case S_IFREG:
        	   printf( "--%s\n", dbuf->d_name ); // Display files
        	   break;

           case S_IFDIR:
        	   printf( "\033[01;34m--%s \033[0m \n", dbuf->d_name );	 //Display in Blue color Folders
        	   break;
           default:
               break;
        }
	}
    exit(1);

	} //end fork
    else {
			wait(NULL);
	}

closedir( dp );
chdir( cwd );

 return 0;

}

FUNC_TYPE cd_func(int num_tokens, char* tokens[])
{
	if(num_tokens == 1) {
		chdir(getenv("HOME"));

	} else {
		chdir(tokens[1]);
	}

	return 0;
}

FUNC_TYPE setenv_func(int num_tokens, char* tokens[]){

		if(num_tokens == 1) {
			printf("INFO: Please enter a variable name\n");
			return 0;
		}

		if (getenv(tokens[1]) != NULL) {
			printf("WARNING: Variable \"%s\" is already defined and overwritten\n", tokens[1]);
		}

		if(tokens[2]!=NULL) {
			setenv(tokens[1],tokens[2],1);

		} else {
			setenv(tokens[1],"",1);

		}

		return 0;
}


FUNC_TYPE unsetenv_func(int num_tokens, char* tokens[]){

	if (getenv(tokens[1]) != NULL) {
		unsetenv(tokens[1]);

	} else {
		printf("WARNING: Variable \"%s\" is undefined\n", tokens[1]);
	}

	return 0;
}


FUNC_TYPE environ_func(int num_tokens, char* tokens[]){

	int k=0;

	while(environ[k]!= NULL) {   //char **environ is defined in unistd.h keeps env variables
		printf("%s\n",  environ[k]);
		k++;
	}
	return 0;
}

FUNC_TYPE external_func(int num_tokens, char* tokens[]){

	int i;
	int k;
	char *extcmd[50]={0};
	char *outfile={0};
	char *infile={0};

	for(k=0; k<num_tokens; k++) {

		if(!strcmp(tokens[k],"<")) {
			infile=(char *)malloc((strlen(tokens[k+1])+1)*sizeof(char));
			strcpy(infile,tokens[k+1]);
			k++;
			continue;
			}
		if(!strcmp(tokens[k],">")) {
			outfile=(char *)malloc((strlen(tokens[k+1])+1)*sizeof(char));
			strcpy(outfile,tokens[k+1]);
			k++;
			continue;
		}

		extcmd[k]=(char *)malloc((strlen(tokens[k])+1)*sizeof(char));
		strcpy(extcmd[k],tokens[k]);
	}

		if(fork() == 0) {
			int in, out, acctest;

			if(infile != NULL) {
				acctest = access(infile, F_OK | R_OK);
				if(acctest < 0) {
					printf("errno is %d\n", errno);
					printf("cannot access the file \"%s\"\n", infile);
					exit(1);
				} else {
					in = open(infile, O_RDONLY);
					dup2(in,0);
					close(in);
				}
			}
			
			if(outfile != NULL) {
				out = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
				dup2(out, 1);
				close(out);
			}

			i = execvpe(extcmd[0], extcmd, envp);
			printf("errno is %d\n", errno);
			if(i < 0) {
				printf("%s: %s\n", extcmd[0], "command not found");
				exit(1);
			}
		} else {
			wait(NULL);
		}
	 return 0;
}


FUNC_TYPE pipetrial_func(int num_tokens, char* tokens[])
{
	int status;
    FUNC_TYPE garbage = 0;
	int pipepos[10]={0}; //keeps pipes position in the tokens
	int i=0; //various integers for loops and array indexes
	int b=0;
	int k=0;
	int a=0;
	int j=0;
	int e=0;
	int num_pipes=0;
	char *split[100]={0}; //keeps splitted commands arguments, delimiter is "|"
	char *tokenscopy[50]={0}; //string array for local copy of tokens

	//Copy all tokens to local function to avoid conflicts between child & parent processes
	for(k=0; k<num_tokens; k++) {
		tokenscopy[k]=(char *)malloc((strlen(tokens[k])+1)*sizeof(char));
		strcpy(tokenscopy[k],tokens[k]);
	}

	//Get number of pipes and their positions
	for(i=0; i<num_tokens;i++) {
		if(!strcmp(tokenscopy[i],"|")){
			pipepos[num_pipes]= i;
			num_pipes++;
		}

		if(!strcmp(tokenscopy[num_tokens-1],"|"))
			{ printf("Invalid null command\n");
			  return 0;

			}

	}

  //create file descriptors
	int c;
	int pipes[num_pipes*2];
	for(c=0; c<num_pipes*2; c=c+2) {
		pipe(pipes + c);
	}

  i=0; //reset i for child processes;

  //for for first command
  if (fork() == 0) {

	  //replace stdout of first argument with write end of 1st pipe
	  dup2(pipes[1], 1);

	  //close all unnecessary ends of pipe
      for (b=0; b<num_pipes*2; b++) {
    	  close(pipes[b]);
      }

    //split commands and its arguments until first pipe
    for(k=0; k<pipepos[0]; k++) {
    	split[i]=(char *)malloc((strlen(tokenscopy[k])+1)*sizeof(char));
    	strcpy(split[i],tokenscopy[k]);
    	i++;
    }

    split[i]=NULL;
    //execute first command

    j = check_internal_command(split[0]);
  		   	      if ( j >= 0) {
  		   	    	  	garbage = mainCommandList[j].FunctionName(i, split);
  		   	      		exit(0);

  		   	      } else {
  		   	         	e = execvpe(split[0], split, envp);
		   	         	printf("errno is %d\n", errno);
		   	         	if(e < 0) {
		   	         		printf("%s: %s\n", split[0], "command not found");
		   	         		exit(1);
		   	         	}
  		   	      }



  } else {
	 // fork for commands which are between pipes
	 //(i.e for 5 pipes, this block handles commands 2 to 5)
	  for(a=0; a<num_pipes-1; a++) {
	   if (fork() == 0){

	 // replace nth commands stdin with read end of n-1 th pipe
		   dup2(pipes[a*2], 0);

	  //split commands and its arguments between pipes
		   i=0;
		   for(k=pipepos[a]+1; k<pipepos[a+1]; k++) {
			   split[i]=(char *)malloc((strlen(tokenscopy[k])+1)*sizeof(char));
			   strcpy(split[i],tokenscopy[k]);
			   i++;
		   }
		   split[i]=NULL; //guarrantes that last argument of string array is NULL

	  // replace nth commands stdout with write end of n+1 th pipe
		   dup2(pipes[a*2+3], 1);

	  // close all ends of pipes
		   for (b=0; b<num_pipes*2; b++) {
    	   close(pipes[b]);
		   }
		   j = check_internal_command(split[0]);
		   	      if ( j >= 0) {
		   	    	  	garbage = mainCommandList[j].FunctionName(i, split);
		   	      		exit(0);

		   	      } else {
		   	         	e = execvpe(split[0], split, envp);
		   	         	printf("errno is %d\n", errno);
		   	         	if(e < 0) {
		   	         		printf("%s: %s\n", split[0], "command not found");
		   	         		exit(1);
		   	         	}
		   	      }
	   } //end fork
	  }

	  // fork for last command
	  if (fork() == 0)
	    {
		  //split last command and its arguments
		    i=0;
		  	for(k=pipepos[num_pipes-1]+1; k<num_tokens; k++) {
		    	split[i]=(char *)malloc((strlen(tokenscopy[k])+1)*sizeof(char));
		    	strcpy(split[i],tokenscopy[k]);
		    	i++;
		    	}
		  split[i]=NULL;

	      dup2(pipes[2*(num_pipes-1)], 0);

	      // close all ends of pipes
	      for (b=0; b<num_pipes*2; b++) {
	    	  close(pipes[b]);
	      }

	      j = check_internal_command(split[0]);
	      if ( j >= 0) {
	    	  	garbage = mainCommandList[j].FunctionName(i, split);
	      		exit(0);

	      } else {
	         	e = execvpe(split[0], split, envp);
	         	printf("errno is %d\n", errno);
	         	if(e < 0) {
	         		printf("%s: %s\n", split[0], "command not found");
	         		exit(1);
	         	}
	      }
	    } //end fork
  }

  //parent process closes all open ends of pipes
  for (b=0; b<num_pipes*2; b++) {
	  close(pipes[b]);
  }

  //wait for child to processes
  for (i = 0; i < num_pipes + 1; i++)
  wait(&status);

  return 0;
}

redirection io_redirection(int num_tokens, char* tokens[]) {

    int k;
    redirection stio ;
	char * outfile= {0};
	char * infile={0};
    stio.f_bkg &= 0 ;    // clear flags
    stio.f_pipe &= 0 ;
    stio.f_rdirect &= 0 ;
    stio.f_support &= 0 ;

	for(k=0; k<num_tokens; k++) {

		if(!strcmp(tokens[k],"<")) {
			infile=(char *)malloc((strlen(tokens[k+1])+1)*sizeof(char));
			strcpy(infile,tokens[k+1]);
            stio.f_rdirect = 1 ;
			k++;
			continue;
			}
		if(!strcmp(tokens[k],">")) {
			outfile=(char *)malloc((strlen(tokens[k+1])+1)*sizeof(char));
			strcpy(outfile,tokens[k+1]);
            stio.f_rdirect |= 2 ;
			k++;
			continue;
            }

        if(!strcmp(tokens[k],"&")) {
            stio.f_bkg |= 1 ;//set a flag in struct   to implement background exec !!
            k++;
            continue;
        }

	}
    stio.infile = infile ;
    stio.outfile = outfile ;
    stio.f_support = (unsigned int)comb_support(stio);

	return stio;

}


FUNC_TYPE background_func(int num_tokens, char* tokens[]) {

	int i;
	int k;
	char *extcmd[50]={0};

	for(k=0; k<num_tokens-1; k++) { //exclude & sign from tokens using -1

		extcmd[k]=(char *)malloc((strlen(tokens[k])+1)*sizeof(char));
		strcpy(extcmd[k],tokens[k]);

	}

	if(fork() == 0) {
		 if(fork() == 0) {
			 int j;
			 FUNC_TYPE garbage;
			 setpgrp(); //change process group
			 //getdtablesize() returns the maximum number of files a process can have open, one more than the largest possible value for a file descriptor
			 for (i=getdtablesize();i>=0;--i) close(i);
			 i=open("/dev/null",O_RDWR);
			 dup(i); //stdout - //returns a second file descriptor that points to the same file table entry as fd does
			 dup(i); //stderr
			 umask(077);
			 chdir("/tmp");
			 signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
			 signal(SIGTTOU,SIG_IGN);
			 signal(SIGTTIN,SIG_IGN);
			  j = check_internal_command(extcmd[0]);
			 if (  j >= 0) {
    				garbage = mainCommandList[j].FunctionName(num_tokens, extcmd);
    				exit(0);

			 } else {
	            	 i = execvpe(extcmd[0], extcmd, envp);
			 		 printf("errno is %d\n", errno);
			    	 if(i < 0) {
			          	printf("%s: %s\n", extcmd[0], "command not found");
			         	exit(1);
			         }
			 }

		 } else {
			 exit(0);
		 }
	} else {
		wait(NULL);
	}

	return 0;
}

int comb_support(redirection x) {

 if ((x.f_rdirect == 1))
      return 1 ;

 if ( (x.f_rdirect == 0) && (x.f_pipe == 1) )
      return 0 ;

 return 0 ;
}


int check_internal_command(char * usercommand) {
	int j=0;
	int command_found = -1;
	for(j=0;j<8;j++) {

			if(!strcmp(usercommand, mainCommandList[j].CommandName)){
				command_found = j;
				break;
			}
	}
    return command_found;
}
